#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include "la.h"
#include "error.h"

typedef enum State {
    START, S_IDENT, S_NUM, S_STR, S_CHAR, S_LEQ, S_GEQ, S_NEQ, S_DEQ, DONE,
//                          ""      ''     <=     >=     !=     ==
} State;

static ReservedWord reserved_words[RESERVED_NUM] = {
    {"const", CONST}, {"int", INT}, {"float", FLOAT}, {"char", CHAR},
    {"if", IF}, {"else", ELSE}, {"for", FOR}, {"while", WHILE},
    {"scanf", SCANF}, {"printf", PRINTF},
    {"void", VOID}, {"main",MAIN},{"return",RETURN},
};

static char line_buf[MAX_LINE] = {0,};
static int buf_size = 0;
static int pre_row = 0;
static int pre_col = 0;

TokenType current_token;
char token_buf[MAX_TOKEN_LEN] = {0,};
int row = 0;
int col = 0;

TokenType key_word_lookup()
{
	int i;
	for (i = 0; i < RESERVED_NUM; i++) {
		if (!strcmp(token_buf, reserved_words[i].word)) {
			return reserved_words[i].type;
		}
	}
	return IDENT;
}

void to_upper(char *str)
{
    unsigned int i;
    for (i = 0; i < strlen(str); i++) {
        str[i] = (char)toupper(str[i]);
    }
}

char get_next_char()
{
    if (col == buf_size) {
        pre_row = row;
        if (fgets(line_buf, MAX_LINE, source) == NULL) {
            print_fatal();
        };
        pre_col = 0;
        col = 0;
        row++;
        buf_size = strlen(line_buf);
    }
    if (col < 0 || col >= buf_size) {
        print_fatal();
    }
    return line_buf[col++];
}

void put_back_char()
{
    if (col != 0) {
        col--;
    }
}

void state_end_set(Bool *track, State *current_state, Bool pb)
{
    *track = FALSE;
    *current_state = DONE;
    if (pb == TRUE) {
        put_back_char();
    }
}

TokenType get_token()
{
    State current_state;
    char current_char;
    Bool track;
    int token_buf_p;

    token_buf_p = 0;
    pre_row = row;
    pre_col = col;
    current_state = START;
    while (current_state != DONE) {
        track = FALSE;
        current_char = get_next_char();
        switch (current_state) {
            case START:
                if (isalpha(current_char) || current_char == '_') {
                    track = TRUE;
                    current_token = IDENT;
                    current_state = S_IDENT;
                }
                else if (isdigit(current_char)) {
                    track = TRUE;
                    //assume it is a integer now.
                    current_token = C_INT;
                    current_state = S_NUM;
                }
                else if (current_char == '\"') {
                    //track const string at next pass.
                    current_token = C_STR;
                    current_state = S_STR;
                }
                else if (current_char == '\'') {
                    current_token = C_CHAR;
                    current_state = S_CHAR;
                }
                else if (current_char == '!') {
                    current_state = S_NEQ;
                }
                else if (current_char == '<') {
                    current_token = LSS;
                    current_state = S_LEQ;
                }
                else if (current_char == '=') {
                    current_token = ASSIGN;
                    current_state = S_DEQ;
                }
                else if (current_char == '>') {
                    current_token = GTR;
                    current_state = S_GEQ;
                }
                else if (isspace(current_char)) {
                    if (feof(source)) {
                        current_state = ENDOFFILE;
                    }
                    else {
                        current_state = START;
                    }
                }
                else {                    
                    switch (current_char) {
                        case '+':
                            current_token = PLUS;
                            break;
                        case '-':
                            current_token = MINUS;
                            break;
                        case '*':
                            current_token = TIMES;
                            break;
                        case '/':
                            current_token = SLASH;
                            break;
                        case ',':
                            current_token = COMMA;
                            break;
                        case ';':
                            current_token = SEMICOLON;
                            break;
                        case '{':
                            current_token = L_CURLY_BRACE;
                            break;
                        case '}':
                            current_token = R_CURLY_BRACE;
                            break;
                        case '(':
                            current_token = LPAREN;
                            break;
                        case ')':
                            current_token = RPAREN;
                            break;
                        case EOF:
                            break;
                    }
                    current_state = DONE;
                }
                break;
            case S_IDENT:
                track = TRUE;
                if (!isalnum(current_char) && current_char != '_') {
                    state_end_set(&track, &current_state, TRUE);
                }
                break;
            case S_NUM:
                track = TRUE;
                if (current_char == '.') {
                    current_token = C_FLOAT;
                }
                else if (!isdigit(current_char)) {
                    state_end_set(&track, &current_state, TRUE);
                }
                break;
            case S_STR:
                track = TRUE;
                if (current_char == '\"') {
                    state_end_set(&track, &current_state, FALSE);
                    break;
                }
                if (!isprint(current_char)) {
                    print_error("Expecting a printable character.", "", "\"");
                    state_end_set(&track, &current_state, TRUE);
                }
                break;
            case S_CHAR:
                track = TRUE;
                if (current_char == '\'') {
                    state_end_set(&track, &current_state, FALSE);
                    break;
                }
                if (!isalnum(current_char) && current_char != '+' && current_char != '-' && current_char != '*' && current_char != '/') {
                    print_error("Expecting a PLUS, MINUS, ALPHABET, or NUMBER", "", "\'");
                    state_end_set(&track, &current_state, TRUE);
                }
                break;
            case S_NEQ:
                if (current_char == '=') {
                    current_token = NEQ;
                    current_state = DONE;
                }
                break;
            case S_LEQ:
                if (current_char == '=') {
                    current_token = LEQ;
                    current_state = DONE;
                }
                else {
                    current_token = LSS;
                    state_end_set(&track, &current_state, TRUE);
                }
                break;
            case S_DEQ:
                if (current_char == '=') {
                    current_token = DEQ;
                    current_state = DONE;
                }
                else {
                    current_token = ASSIGN;
                    state_end_set(&track, &current_state, TRUE);;
                }
                break;
            case S_GEQ:
                if (current_char == '=') {
                    current_token = GEQ;
                    current_state = DONE;
                }
                else {
                    current_token = GTR;
                    state_end_set(&track, &current_state, TRUE);
                }
                break;
            default:
                current_token = ERROR;
                print_error("Illegal character was encountered.", "", "");
                state_end_set(&track, &current_state, FALSE);
        }
        if (track == TRUE) {
            if (token_buf_p == MAX_TOKEN_LEN - 1) {
                token_buf[token_buf_p++] = '\0';
                print_warning("Length of the token is so long so that it will be trimmed out.", token_buf);
            }
            if (token_buf_p < MAX_TOKEN_LEN) {
                token_buf[token_buf_p++] = line_buf[col - 1];
            }
        }
        if (current_state == DONE) {
            token_buf[token_buf_p] = '\0';
            if (current_token == C_FLOAT || current_token == C_INT) {
                if (token_buf[0] == '0' && token_buf_p > 1) {
                    print_error("Leading zero within FLOAT and INT is not allowed.", token_buf, NULL);
                }
			}
			if (current_token == IDENT) {
				current_token = key_word_lookup();
				if (current_token == IDENT) {
				    to_upper(token_buf);
				}
			}
        }
        if (current_state == ENDOFFILE) {
            break;
        }
    }
	return current_token;
}

// skip to next character after SEMICOLON or to next line.
void skip(char *skp_str)
{
    char *p;
    print_warning("\tLexer is trying to skip.", skp_str);
    p = strpbrk(&line_buf[col], skp_str);
    col += p - &line_buf[col];
}

void move_forward()
{
    if (col != buf_size) {
        col++;
    }
}

void read_token()
{
    while (get_token() == ERROR) {
        ;
    }
    if (DEBUG == TRUE) {
        fprintf(stdout, "ROW: %d, COL: %d: %d, %s\n", row, col, current_token, token_buf);
    }
}

void put_back()
{
    col = pre_col;
}